/**
 * 
 */
package de.fsujena.chat_pi.connection;

import java.io.*;
import java.net.*;

import de.fsujena.chat_pi.message.FileMessage;
import de.fsujena.chat_pi.message.Message;

/**
 * The connector provides a connection between a server and a client.
 * These are streams between the two instances and methods to send
 * messages, objects or texts between them.
 * 
 * It also offers the possibility to secure the connection using RSA.
 * 
 * @author Erik Hebestreit
 * @author David Reifert
 */
public class Connector {
	private Socket socket;
	
	private boolean useRSA;
	private boolean isClient;
	
	private RSAencryption rsaEnc;

	private InputStream in;
	private OutputStream out;

	private InputStreamReader isr;
	private OutputStreamWriter osw;

	private ObjectInputStream ois;
	private ObjectOutputStream oos;

	private BufferedReader bin;
	private BufferedWriter bout;

	private PrintWriter pout;

	/**
	 * Create a new connection and if necessary the RSA encryption.
	 * 
	 * @throws RSAException 
	 */
	public Connector(Socket socket, boolean useRSA, boolean isClient) throws RSAException {
		this.socket = socket;
		this.useRSA = useRSA;
		this.isClient = isClient;

		if (useRSA) {
			rsaEnc = new RSAencryption();
		}
	}

	/**
	 * Initialize the connection by setting up all the streams. If RSA encryption is
	 * activated also exchange keys and initialize cipher streams.
	 * 
	 * @throws IOException if an error occurs during setup of a new connection
	 * @throws RSAException if an error occurs during setup of the RSA encryption
	 */
	public boolean init() throws IOException, RSAException {
		in = socket.getInputStream();
		out = socket.getOutputStream();
		
		isr = new InputStreamReader(in);
		osw = new OutputStreamWriter(out);

		bin = new BufferedReader(isr);
		bout = new BufferedWriter(osw);
		
		if (isClient) {											//check if used by client or server
			ois = new ObjectInputStream(in);
			oos = new ObjectOutputStream(out);
			oos.flush();
		} else {
			oos = new ObjectOutputStream(out);					//the order has to be reversed for the server
			oos.flush();
			ois = new ObjectInputStream(in);
		}

		if (useRSA) {											//use encryption
			if (isClient) {
				rsaEnc.receivePublicKey(ois);					//send/receive Keys
				rsaEnc.sendPublicKey(oos);
			} else {
				rsaEnc.sendPublicKey(oos);
				rsaEnc.receivePublicKey(ois);
			}

			rsaEnc.initializeCipherStreams(in, out);

			in = rsaEnc.cis;
			out = rsaEnc.cos;
		} else {

		}

		pout = new PrintWriter(out, true);
		
		return true;
	}

	/**
	 * Sets whether RSA encryption is used or not.
	 * 
	 * @param useRSA flag indicating, whether RSA is used or not
	 * @throws IOException
	 * @throws RSAException
	 */
	public void setRSA(Boolean useRSA) throws IOException, RSAException {
		if (this.useRSA != useRSA) {
			this.useRSA = useRSA;
			init();
		}
	}

	/**
	 * @return true if RSA is currently in use, and false if not
	 */
	public boolean getRSA() {
		return useRSA;
	}

	/**
	 * Sending a simple string message to the remote console.
	 * 
	 * @param message the String message
	 * @deprecated
	 */
	public void sendString(String message) {
		pout.println(message);
	}

	/**
	 * Read a general object from the stream.
	 * 
	 * @return object, which was read from the stream
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public Object readGeneralObject() throws ClassNotFoundException,IOException {
		Object inputObject = null;

		inputObject = ois.readObject();

		return inputObject;
	}

	/**
	 * Send a general object through the stream.
	 * 
	 * @param obj Object to be sent.
	 * @throws IOException 
	 */
	public void sendGeneralObject(Object obj) throws IOException {
		this.oos.writeObject(obj);
		this.oos.flush();
	}

	/**
	 * Read a string from the stream.
	 * 
	 * @return String which was read from the stream, null if there was no String object.
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public String readStringObject() throws IOException, ClassNotFoundException {
		String string = null;
		Object inputObject;
		
		inputObject = readGeneralObject();
		if (inputObject instanceof String)
			string = (String) inputObject;

		return string;
	}

	/**
	 * Send a string through the stream.
	 * 
	 * @param outputString String to be sent
	 * @throws IOException
	 */
	public void sendStringObject(String outputString) throws IOException {
		sendGeneralObject(outputString);
	}

	/**
	 * Receive a message object through the stream.
	 * 
	 * @return the message object received, null if there was no message object
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public Message readMessageObject() throws ClassNotFoundException, IOException {
		Message message = null;
		Object inputObject = readGeneralObject();
		
		if (inputObject instanceof Message)
			message = (Message) inputObject;

		return message;
	}

	/**
	 * Send a message object through the stream
	 * 
	 * @param message Message to be sent (can also be a TextMessage, or SysMessage)
	 * @throws IOException
	 */
	public void sendMessageObject(Message message) throws IOException {
		sendGeneralObject(message);
	}
	
	/**
	 * Send a file message.
	 * 
	 * NOT YET IMPLEMENTED!
	 * 
	 * @param fileMessage
	 * @deprecated
	 */
	public void sendMessageObject(FileMessage fileMessage) {
		/*
		 *  TODO: implement sending a file in an extra thread.
		 *  Idea: the FileMessage object contains informations about the file
		 *  and is sent in advance. Afterwards the receiver opens a receiver thread
		 *  and the sending process is started.
		 */
	}
	
	/**
	 * Receive a file.
	 * 
	 * NOT YET IMPLEMENTED!
	 */
	public void receiveFile() {
		// TODO: To be implemented - see sendMessageObject(FileMessage)
	}

}
